/*
* Copyright (c) 2009, Roc King
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of the OwnWaterloo,Roc King nor the
*       names of its contributors may be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <assert.h>
#include <string.h>

#include "callback_currying/stdcall_partial_application.h"

#ifndef CALLBACK_CURRYING_NO_PORTABILITY_SIZE_CHECK
static const char g_32bit_check[sizeof(uintptr_t)==sizeof(uint32_t)?1:-1] = {0};
static const char g_size_check
[sizeof(stdcall_partial_application)==CC_STDCALL_THUNK_SIZE?1:-1] = {0};
#endif  /* #ifndef CALLBACK_CURRYING_NO_PORTABILITY_SIZE_CHECK */

enum stdcall_partial_application_offset {
    offset_to_fixed_argument    = 2,
    offset_to_jmp_instruction   = 7,
    offset_to_applied_function  = 8,
};

void stdcall_initialize(stdcall_partial_application* papply)
{
    /** the layout of machine code */
    static const stdcall_partial_application stdcall_machine_code= { {
        0x58,                   /* pop eax */
        0x68,                   /* push (dword imm) */
        0xcc,0xcc,0xcc,0xcc,    /* fixed argument placeholder */
        0x50,                   /* push eax */
        0xe9,                   /* jmp (dword offset) */
        0xcc,0xcc,0xcc,0xcc,    /* offset to function placeholder */
    } };
    /* 0xcc is the intermit 3 instruction */

    if (!papply)
    {
        assert(0);
        return;
    }
    memcpy(papply,&stdcall_machine_code,CC_STDCALL_THUNK_SIZE);
}

void stdcall_set_applied_function(stdcall_partial_application* papply
                                 ,uintptr_t applied_function)
{
    uintptr_t offset_to_applied = 0;
    if (!papply)
    {
        assert(0);
        return;
    }
    offset_to_applied = applied_function - 5
               - (uintptr_t)&papply->machine_code[offset_to_jmp_instruction];

    memcpy(&papply->machine_code[offset_to_applied_function],&offset_to_applied
          ,sizeof(offset_to_applied));
}

uintptr_t
stdcall_get_applied_function(const stdcall_partial_application* papply)
{
    uintptr_t offset_to_applied = 0;
    if (!papply)
    {
        assert(0);
        return 0;
    }
    memcpy(&offset_to_applied,&papply->machine_code[offset_to_applied_function]
        ,sizeof(offset_to_applied));
    return offset_to_applied + (uintptr_t)papply + offset_to_jmp_instruction+5;
}

void stdcall_set_fixed_argument(stdcall_partial_application* papply
                               ,uintptr_t fixed_argument)
{
    if (!papply)
    {
        assert(0);
        return;
    }
    memcpy(&papply->machine_code[offset_to_fixed_argument],&fixed_argument
        ,sizeof(fixed_argument));
}

uintptr_t
stdcall_get_fixed_argument(const stdcall_partial_application* papply)
{
    uintptr_t argument = 0;
    if (!papply)
    {
        assert(0);
        return 0;
    }
    memcpy(&argument
        ,&papply->machine_code[offset_to_fixed_argument],sizeof(argument));
    return argument;
}
